from netplug import *
from utils import *
import SocketServer
from cmd import *
import struct
import SocketServer
import socket
import signature


class tcpNetPlug(netPlug):
    """class for tcp netplug"""
    def __init__(self,netPlugParam,netserver=None):
        netPlug.__init__(self,netPlugParam,netserver)
        self._port = self._netPlugParam.getSpecific(NETPLUG_SPECIFIC_TCP_PORT)
        self._ip = self._netPlugParam.getSpecific(NETPLUG_SPECIFIC_TCP_IP)
        self._server = None
    
    def run(self):
        """activate netPlug : enable listenning/sending capabilities (server side)"""
        infoOutput("NETPLUG : start netplug %s (type : %s)"%(self._netPlugParam.getName(),self._netPlugParam.getType()))
        try:
            self._server = myTCPServer(('localhost',self._port),MyTCPHandler)
            self._server.setCmdHandler(self.handleCmd)
            self._server.serve_forever()
        except:
            errorOutput("NETPLUG : impossible to open port %d for netplug %s"%(self._port,self._netPlugParam.getName()))

        
                
    def stop(self):
        """stop netPlug"""
        infoOutput("NETPLUG : stop netplug %s (type : %s)"%(self._netPlugParam.getName(),self._netPlugParam.getType()))
        self._server.shutdown()
    
    def sendCmd(self,cmd):
        """send cmd to netplug (client side)
        return response cmd
        if errors, return None"""
        #convert cmd to bin stream
        data = serializeCmdToBin(cmd)
        if not data:
            return None
        #send data
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
            s.connect((self._ip,self._port))
            s.send(data)
        except:
            infoOutput("NETPLUG TCP : Connexion error to %s:%d"%(self._ip,self._port))
            s.close()
            return None
        infoOutput("NETPLUG TCP : Send cmd type %d to %s:%d"%(cmd.getType(),self._ip,self._port))
        #wait response
        data = s.recv(1)
        if data == '\x69':   #magic number for protocol
            size = s.recv(2) #taille global cmd
            size = struct.unpack('h',size)[0]
            data = s.recv(size)
            #parse data
            res = parseBinToCmd(data)
            if not res:
                warningOutput("Unknown data received from tcp netplug")
                return None
            if res.getType() != CMD_TYPE_RESP:
                warningOutput("Unhandled command received from tcp netplug : %d"%(res.getType(),))
                s.close()
                return None
        else:
            if data:
                warningOutput("Unknown data received from tcp netplug : %d"%(ord(data),))
            s.close()
            return None
        s.close()
        infoOutput("NETPLUG TCP : Received cmd type %d from %s:%d"%(res.getType(),self._ip,self._port))
        return res   
        
    
class myTCPServer(SocketServer.TCPServer):
    def setCmdHandler(self,handler):
        self._cmdHandler = handler
    
    def handleCmd(self,cmd):
        return self._cmdHandler(cmd)
    
class MyTCPHandler(SocketServer.BaseRequestHandler):
    """    The RequestHandler class for our server.    """

    def handle(self):
        data = self.request.recv(1)
        if data == '\x69':   #magic number for protocol
            size = self.request.recv(2) #taille global cmd
            size = struct.unpack('h',size)[0]
            data = self.request.recv(size)
            #parse data
            req = parseBinToCmd(data)
            if not req:
                #incorrect data stream received
                return 
            infoOutput("NETPLUG TCP : received cmd type %d from %s"%(req.getType(),self.client_address))
            #send received cmd to server
            respCmd = self.server.handleCmd(req)
            #send response to peer
            data = serializeCmdToBin(respCmd)
            if data:
                self.request.send(data)
                infoOutput("NETPLUG TCP : send cmd type %d to %s"%(respCmd.getType(),self.client_address))
                    

def safeTCPCmdConsumer(data):
    """take a data stream in input
    return a tuple of a string (data read) and the remaining data
    if error, return None and the data"""
    size = struct.unpack('h',data[0:2])[0]
    if size <= len(data)+2:
        return (data[2:size+2],data[size+2:])
    else:
        return (None,data)
    
def parseBinToCmd(data):
    """parse binary stream to cmd object
    return None if error"""
    if len(data) < 1:
        warningOutput("Bad data size in command received")
        return None
    type = struct.unpack('B',data[0])[0]
    data = data[1:]
    if type == CMD_TYPE_GET:
        cmd = getCmd()
        (id,data) = safeTCPCmdConsumer(data)
        if id != None:
            cmd.setID(id) #get ID
        else:
            warningOutput("Bad data size for id in command received")
            return None
        (authorID,data) = safeTCPCmdConsumer(data)
        if authorID != None:
            cmd.setAuthorID(authorID) #get authorID   
        else:
            warningOutput("Bad data size for authorid in command received")
            return None
    elif type == CMD_TYPE_GETVER:
        cmd = getVerCmd()
        (id,data) = safeTCPCmdConsumer(data)
        if id != None:
            cmd.setID(id) #get ID
        else:
            warningOutput("Bad data size for id in command received")
            return None
        (authorID,data) = safeTCPCmdConsumer(data)
        if authorID != None:
            cmd.setAuthorID(authorID) #get authorID   
        else:
            warningOutput("Bad data size for authorid in command received")
            return None
    elif type == CMD_TYPE_RESP:
        cmd = respCmd()
        code = struct.unpack('B',data[0])[0]
        cmd.setCodeRep(code) #get response code
        data = data[1:]
        (d,data) = safeTCPCmdConsumer(data)
        if d != None:
            cmd.setData(d) #get data
        else:
            warningOutput("Bad data size for data in command received from tcp netplug")
            return None
    else:
        warningOutput("Unknown command received : %d"%(type,))
        return None
    (requesterID,data) = safeTCPCmdConsumer(data)
    if requesterID != None:
        cmd.setSenderID(requesterID) #get requesterID   
    else:
        warningOutput("Bad data size for requesterID in command received")
        return None
    (sign,data) = safeTCPCmdConsumer(data)
    if sign != None:
        signObj = signature.signature()
        signObj.loads(sign)
        cmd.setSignature(signObj) #get signature   
    else:
        warningOutput("Bad data size for signature in command received")
        return None
    return cmd

def serializeCmdToBin(cmdObj):
    """convert cmd to bytes stream
    return None if error"""
    data = ''
    if cmdObj.getType() == CMD_TYPE_GET:
        data += struct.pack('B',CMD_TYPE_GET)
        data += struct.pack('h',len(cmdObj.getID()))
        data += cmdObj.getID()
        data += struct.pack('h',len(cmdObj.getAuthorID()))
        data += cmdObj.getAuthorID()
        data += struct.pack('h',len(cmdObj.getSenderID()))
        data += cmdObj.getSenderID()
        data += struct.pack('h',len(cmdObj.getSignature().dumps()))
        data += cmdObj.getSignature().dumps()
        data = '\x69' + struct.pack('h',len(data)+3) + data #add magic number + size
    elif cmdObj.getType() == CMD_TYPE_GETVER:
        data += struct.pack('B',CMD_TYPE_GETVER)
        data += struct.pack('h',len(cmdObj.getID()))
        data += cmdObj.getID()
        data += struct.pack('h',len(cmdObj.getAuthorID()))
        data += cmdObj.getAuthorID()
        data += struct.pack('h',len(cmdObj.getSenderID()))
        data += cmdObj.getSenderID()
        data += struct.pack('h',len(cmdObj.getSignature().dumps()))
        data += cmdObj.getSignature().dumps()
        data = '\x69' + struct.pack('h',len(data)+3) + data #add magic number + size
    elif cmdObj.getType() == CMD_TYPE_RESP:
        data += struct.pack('B',CMD_TYPE_RESP)
        data += struct.pack('B',cmdObj.getCodeRep())
        data += struct.pack('h',len(cmdObj.getData()))
        data += cmdObj.getData()
        data += struct.pack('h',len(cmdObj.getSenderID()))
        data += cmdObj.getSenderID()
        data += struct.pack('h',len(cmdObj.getSignature().dumps()))
        data += cmdObj.getSignature().dumps()
        data = '\x69' + struct.pack('h',len(data)+3) + data #add magic number + size
    else:
        errorOutput("TCP netplug : Unknown command type to serialize : %d"%(cmd.getType(),))
        return None
    return data